/*****************************************************************************/
/*                                                                           */
/*    dosKernel - Domino Operation System Kernel Module                      */
/*                                                                           */
/*    Copyright (C) 2005 Laszlo Arvai                                        */
/*                                                                           */
/*    ------------------------------------------------------------------     */
/*    krnlMessageQueue.c - Message queue                                     */
/*****************************************************************************/

///////////////////////////////////////////////////////////////////////////////
// Includes
#include "krnlMessageQueue.h"
#include "krnlMessages.h"
#include "krnlMessageMap.h"
#include "sysSettings.h"
#include "krnlMain.h"

//////////////////////////////////////////////////////////////////////////////
// Constants
#define krnlINVALID_MESSAGE_QUEUE_INDEX 255

///////////////////////////////////////////////////////////////////////////////
// Module local variables

// message queue
static krnlMessage l_message_queue[krnlMESSAGE_QUEUE_LENGTH];

// queue pointers
volatile dosByte l_first_empty_message_index = krnlINVALID_MESSAGE_QUEUE_INDEX;
volatile dosByte l_last_empty_message_index = krnlINVALID_MESSAGE_QUEUE_INDEX;
volatile dosByte l_first_message_index = krnlINVALID_MESSAGE_QUEUE_INDEX;
volatile dosByte l_last_message_index = krnlINVALID_MESSAGE_QUEUE_INDEX;

// flag for async messages
volatile dosByte l_async_first_message_index = krnlINVALID_MESSAGE_QUEUE_INDEX;
volatile dosByte l_async_last_message_index = krnlINVALID_MESSAGE_QUEUE_INDEX;

// flags for message reposting
volatile dosBool l_message_repost = dosFalse;

///////////////////////////////////////////////////////////////////////////////
// Initialize message system
void krnlInitMessageQueue(void)
{
  dosByte i;

  // link empty messages into a chain
  for( i = 0; i < krnlMESSAGE_QUEUE_LENGTH-1; i++ )
    l_message_queue[i].NextMessageIndex = i+1;

  l_message_queue[krnlMESSAGE_QUEUE_LENGTH-1].NextMessageIndex = krnlINVALID_MESSAGE_QUEUE_INDEX;

  // initialize pointers
  l_first_empty_message_index = 0;
  l_last_empty_message_index  = krnlMESSAGE_QUEUE_LENGTH - 1;
  l_first_message_index = krnlINVALID_MESSAGE_QUEUE_INDEX;
  l_last_message_index = krnlINVALID_MESSAGE_QUEUE_INDEX;

  l_async_first_message_index = krnlINVALID_MESSAGE_QUEUE_INDEX;
  l_async_last_message_index = krnlINVALID_MESSAGE_QUEUE_INDEX;
  
  l_message_repost = dosFalse;
}

///////////////////////////////////////////////////////////////////////////////
// Allocate message storage
krnlMessage* krnlAllocateMessage(void)
{
  dosByte message_index;
  dosByte free_message_index = krnlINVALID_MESSAGE_QUEUE_INDEX;
 
  // try to allocate the last empty message message storage space
  // if there is no last -> no empty space
  if( l_last_empty_message_index != krnlINVALID_MESSAGE_QUEUE_INDEX )
  {
    // if only one message storage is empty -> allocate it
    // and invalidate indices
    if( l_first_empty_message_index == l_last_empty_message_index )
    {
      // return the only one storage
      free_message_index = l_first_empty_message_index;

      // invalidate indices (no more empty space)
      l_first_empty_message_index = krnlINVALID_MESSAGE_QUEUE_INDEX;
      l_last_empty_message_index  = krnlINVALID_MESSAGE_QUEUE_INDEX;
    }
    else
    {
      // return the last message
      free_message_index = l_last_empty_message_index;

      // find previos empty message to update l_last_empty_message
      message_index = l_first_empty_message_index;
      while( message_index != krnlINVALID_MESSAGE_QUEUE_INDEX && 
             l_message_queue[message_index].NextMessageIndex != l_last_empty_message_index )
        message_index = l_message_queue[message_index].NextMessageIndex;

      // if the message found
      if( message_index != krnlINVALID_MESSAGE_QUEUE_INDEX )
      {
        l_last_empty_message_index = message_index;
        l_message_queue[message_index].NextMessageIndex = krnlINVALID_MESSAGE_QUEUE_INDEX;
      }
      else
      {
        // TODO: System fault
#ifdef WIN32        
        printf( "System fault\n" );
#endif
      }
    }
  }

  // initialize message
  if( free_message_index != krnlINVALID_MESSAGE_QUEUE_INDEX )
  {
    // link to the message queue
    if( l_last_message_index == krnlINVALID_MESSAGE_QUEUE_INDEX )
    {
      l_first_message_index = l_last_message_index = free_message_index;
    }
    else
    {
      l_message_queue[l_last_message_index].NextMessageIndex = free_message_index;
      l_last_message_index = free_message_index;
    }

    l_message_queue[free_message_index].NextMessageIndex = krnlINVALID_MESSAGE_QUEUE_INDEX;

    // return message address
    return &l_message_queue[free_message_index];
  }
  else
  {
    // no empty storage found
    return DOS_NULL;
  }
}

///////////////////////////////////////////////////////////////////////////////
// Allocate message storage for IRQ routines
krnlMessage* krnlAllocateAsyncMessage(void)
{
  dosByte message_index;

  // allocate message storage from the begining of the empty list
  // to not interfere with normal message allocation at least
  // two entries must be free
  if( l_first_empty_message_index != krnlINVALID_MESSAGE_QUEUE_INDEX && 
      l_last_empty_message_index != krnlINVALID_MESSAGE_QUEUE_INDEX &&
      l_first_empty_message_index != l_last_empty_message_index &&
      l_message_queue[l_first_empty_message_index].NextMessageIndex != l_last_empty_message_index )
  {
    // free entry found
    message_index = l_first_empty_message_index;

    // link to the async message post list
    if( l_async_last_message_index == krnlINVALID_MESSAGE_QUEUE_INDEX )
    {
      l_async_first_message_index = l_async_last_message_index = message_index;
    }
    else
    {
      l_message_queue[l_async_last_message_index].NextMessageIndex = message_index;

      l_async_last_message_index = message_index;
    }

    // update index
    l_first_empty_message_index = l_message_queue[message_index].NextMessageIndex;

    // unlink message
    l_message_queue[message_index].NextMessageIndex = krnlINVALID_MESSAGE_QUEUE_INDEX;

    return &l_message_queue[message_index];
  }
  else
  {
    // TODO: error handling
#ifdef WIN32    
    printf( "Async message has been lost.\n" );
#endif

    return DOS_NULL;
  }
}

///////////////////////////////////////////////////////////////////////////////
// Preprocess messages
void krnlPreprocessMessages( void )
{
  dosByte message_index;

  // link async messages
  if( l_async_last_message_index != krnlINVALID_MESSAGE_QUEUE_INDEX )
  {
    // link to the message queue
    message_index = l_async_first_message_index;
    if( l_last_message_index == krnlINVALID_MESSAGE_QUEUE_INDEX )
      l_first_message_index = l_async_first_message_index;
    else
      l_message_queue[l_last_message_index].NextMessageIndex = l_async_first_message_index;

    // find last message index
    l_async_last_message_index = krnlINVALID_MESSAGE_QUEUE_INDEX;

    while( l_message_queue[message_index].NextMessageIndex != krnlINVALID_MESSAGE_QUEUE_INDEX )
      message_index = l_message_queue[message_index].NextMessageIndex;

    l_last_message_index = message_index;
  }

	// handle idle functions
	krnlIdleHandlers();
}

///////////////////////////////////////////////////////////////////////////////
// Get Message
krnlMessage* krnlGetMessage(void)
{
  if( l_first_message_index == krnlINVALID_MESSAGE_QUEUE_INDEX )
    return DOS_NULL;
  else
    return &l_message_queue[l_first_message_index];
}

///////////////////////////////////////////////////////////////////////////////
// Repost message
void krnlRepostMessage(void)
{
  l_message_repost = dosTrue;
}

///////////////////////////////////////////////////////////////////////////////
// Message Is reposted
dosBool krnlIsMessageReposted(void)
{
  return l_message_repost;
}

///////////////////////////////////////////////////////////////////////////////
// Free message
void krnlFreeMessage(void)
{
  dosByte message_index;

  // unlink message
  message_index = l_first_message_index;
  
  l_first_message_index = l_message_queue[message_index].NextMessageIndex;
  l_message_queue[message_index].NextMessageIndex = krnlINVALID_MESSAGE_QUEUE_INDEX;

  if( l_first_message_index == krnlINVALID_MESSAGE_QUEUE_INDEX )
    l_last_message_index = krnlINVALID_MESSAGE_QUEUE_INDEX;

  // if repost link to the end of the list
  if( l_message_repost )
  {
    l_message_repost = dosFalse;

    if( l_last_message_index == krnlINVALID_MESSAGE_QUEUE_INDEX )
    {
      l_first_message_index = l_last_message_index = message_index;
    }
    else
    {
      l_message_queue[l_last_message_index].NextMessageIndex = message_index;
      l_last_message_index = message_index;
    }
  }
  else
  {
    // if no repost, simply add to the empty list
    if( l_last_empty_message_index == krnlINVALID_MESSAGE_QUEUE_INDEX )
    {
      l_first_empty_message_index = l_last_empty_message_index = message_index;
    }
    else
    {
      l_message_queue[l_last_empty_message_index].NextMessageIndex = message_index;
      l_last_empty_message_index = message_index;
    }
  }
}
